'''
ga算法单变量
求y=10×sin(5x)+7×cos(4x)的最大值\
随机产生种群pop
当迭代次数未到或还未找到满意解，进行如下循环：
1.检查每个染色体，计算适应性分数，评判染色体优劣。
2.从当前群体中选出2个成员。被选出的概率正比于染色体的适应性，适应性分数愈高，被选中的可能性也就愈大。常用的方法就是采用所谓的轮盘赌选择法或赌轮选择法(Roulette wheel selection)。
3.按照一定的交叉概率pc和交叉方法（crossover），生成新的个体。
4.按照一定的变异概率pm和变异方法（mutation），生成新的个体。
5.新一代种群产生，返回第1步。
结束循环
'''
import numpy as np
import matplotlib.pyplot as plt
import math


def main():
    print('y=10×sin(5x)+7×cos(4x)')
    plt_make()
    # 设置迭代次数和仲春数量，以及基因最大值
    pop_size  =500
    upper_limit = 10
    chromosome_length = 10#染色问题长度
    iter = 500
    pc = 0.6 #交叉概率
    pm =0.01#变异概率
    result = []
    pop = init_population(chromosome_length,pop_size)
    best_x = []
    best_y = []
    # 进行迭代
    for i in range(iter):
        obj_value,x = calc_obj_value(pop, chromosome_length, upper_limit)  # 个体评价，有负值
        fit_value = cal_fit(obj_value)
        #计算最佳适度和最佳个体
        best_fit = np.max(fit_value)
        best_individual = pop[np.argmax(fit_value)]
        result.append([x[np.argmax(fit_value)],best_fit])
        #轮盘选择
        pop =selection(pop,fit_value)
        #交叉
        pop = crossover(pop, pc)  # 染色体交叉（最优个体之间进行0、1互换）
        #变异
        pop=mutation(pop,pm)
        if iter % 20 == 0:
            best_x.append(result[-1][0])
            best_y.append(result[-1][1])
    print("x = %f, y = %f" % (result[-1][0], result[-1][1]))
        # 看种群点的选择
    plt.scatter(best_x, best_y, s=3, c='r')
    X1 = [i / float(10) for i in range(0, 100, 1)]
    Y1 = [10 * math.sin(5 * x) + 7 * math.cos(4 * x) for x in X1]
    plt.plot(X1, Y1)
    plt.show()



def plt_make():
    """y = 10 * math.sin(5 * x) + 7 * math.cos(4 * x)"""
    X1 = np.linspace(0,10,101)
    Y1 = 10 * np.sin(5 * X1) + 7 * np.cos(4 * X1)
    plt.plot(X1, Y1)
    plt.show()

def init_population(chromosome_length,pop_size):
    '''
    初始化种群
    :param chromosome_length: 染色体长度
    :param pop_size: 种群数量
    :return: 返回pop[[1,0,1,1,1,1,1,1,0,1],....]
    '''
    pop = np.random.randint(0,2,(pop_size,chromosome_length))
    return pop


def calc_obj_value(pop, chromosome_length, upper_limit):
    '''
    对种群个体进行评价
    :param pop: 种群
    :param chromosome_length:种群长度
    :param upper_limit: 种群上限限制
    :return: 返回的obj_val为个体评价，x为解码的值 a+（a-b）*up_limiter/2**len-1
    '''
    #将二进制转为十进制,进行解码
    pop = pop.dot(2 ** np.arange(chromosome_length)[::-1])
    x = upper_limit*pop/2**(chromosome_length-1)
    obj_val = 10*np.sin(5*x)+7*np.cos(4*x)
    return obj_val,x

def cal_fit(obj_value):
    '''
    :param obj_value: 个体评价，筛选掉负数值
    :return: 返回大于0的适度
    '''
    # 去掉小于0的值，更改c_min会改变淘汰的下限
    # 比如设成10可以加快收敛
    # 但是如果设置过大，有可能影响了全局最优的搜索
    fit_value = []
    c_min = 10
    for value in obj_value:
        if value > c_min:
            temp = value
        else:
            temp = 0.
        fit_value.append(temp)
    # fit_value保存的是活下来的值
    return np.array(fit_value)

def selection(pop,fit_value):
    '''

    :param pop: 种群
    :param fit_value:适度值
    :return: 返回被筛选掉以后的种群
    '''
    total = np.sum(fit_value)
    p_fit_value = fit_value/total
    p_fit_value = np.cumsum(p_fit_value)
    pop_len =pop.shape[1]
    ms =np.sort(np.random.random((10)))
    newpop = pop.copy()
    i = 0
    j = 0
    # print(pop.shape)
    while i<pop_len:
        if ms[i]<p_fit_value[j]:
            newpop[i] =pop[j]
            i+=1
        else:
            j+=1
    return newpop

def crossover(pop, pc):
    '''
    :param pop:种群个数
    :param pc: 交叉概率
    :return: 返回交叉后的种群
    '''
    length = pop.shape[1]
    for i in range(length-1):
        if np.random.random()>pc:
            temp1 =[]
            temp2 =[]
            cpoint = np.random.randint(0, len(pop[0]))
            temp1.extend(pop[i][0:cpoint])
            temp1.extend(pop[i+1][cpoint:len(pop[i])])
            temp2.extend(pop[i+1][0:cpoint])
            temp2.extend(pop[i ][cpoint:len(pop[i])])
            pop[i] = temp1[:]
            pop[i + 1] = temp2[:]
    return pop

def mutation(pop, pm):
    px,py = pop.shape
    # 每条染色体随便选一个杂交
    for i in range(px):
        if (np.random.random() < pm):
            mpoint = np.random.randint(0, py - 1)
            if (pop[i][mpoint] == 1):
                pop[i][mpoint] = 0
            else:
                pop[i][mpoint] = 1
    return pop



if __name__ == '__main__':
    # pop = [[np.random.randint(0, 1) for i in range(10)] for j in range(3)]
    # print(len(pop))
    # print(pop[:])
    main()